/*****************************************************
** Author:
** Date: 2019-10-12
*
*****************************************************/

#ifdef OS_LINUX
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#elif defined(OS_DNA)
#include "dna_libc.h"
#include "dna_keyvalue.h"
#include "dna_sys_tools.h"
#include "dna_cloud.h"
#endif

#include "comm.h"
#include "module.h"
#include "timer.h"
#include "gpio.h"
#include "pwm.h"
#include "uart.h"
#include "dnalua.h"
#include "script.h"

#include "testcase.h"


#ifdef OS_LINUX
#define LUA_ARCH_UART_WRITE(hdl, b, s) \
            uart_write(hdl, b, s)
#define LUA_ARCH_UART_READ(hdl, b, s) \
            uart_read(hdl, b, s)
#define DNA_DEVICE_FRAMEBUFFER_SIZE 256
#elif defined(OS_DNA)
#define LUA_ARCH_UART_WRITE(hdl, b, s) \
            dna_uart_write(hdl, b, s)
#define LUA_ARCH_UART_READ(hdl, b, s) \
            dna_uart_read(hdl, b, s)
#endif

#define LUA_ARCH_TIMER_PERIOD   10 // unit:ms

struct buffmgr
{
    void *buff;
    int maxsize;
    int len;
};

static struct lua_arch
{
    lua_State *l;
    char *script;
    int scrisize;
    int user_wait;
    unsigned int cldsmask;
    struct buffmgr snapshot;
    struct buffmgr cache;

    syscom_handle_t syscom_hdl;
    int syscomid_startup;
    int syscomid_net_notify;
    int syscomid_uart_hdl;

    struct lua_arch_uart uart;
    struct lua_arch_devinfo devinfo;

#ifdef OS_LINUX
    pthread_mutex_t vm_mutex;
    pthread_t       timer;
    pthread_mutex_t sema_mtx;
    pthread_cond_t  sema_cnd;
#elif defined(OS_DNA)
    dna_mutex_handle_t vm_mutex;
    dna_timer_handle_t timer;
    dna_semaphore_handle_t  sema;
#else
#error "unknow OS, not support yet!"
#endif
} lua_arch_ctx;


#define LUA_SYSCOM_STACK_SIZE   (4 * 1024)
#define LUA_ARCH_BIPBUFFER_SIZE (2 * 1024)
#define LUA_ARCH_SNAPSHOT_SIZE  (2 * 1024)
static unsigned char bipbuffer[LUA_ARCH_BIPBUFFER_SIZE];
static unsigned char snapshot_buff[LUA_ARCH_SNAPSHOT_SIZE];
static unsigned char uart_buff[DNA_DEVICE_FRAMEBUFFER_SIZE];
static unsigned char bincache[DNA_DEVICE_FRAMEBUFFER_SIZE];


void lua_arch_lock(void)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    // lua_log_dbg("get lock");
#ifdef OS_LINUX
    pthread_mutex_lock(&ctx->vm_mutex);
#elif defined(OS_DNA)
    dna_mutex_get(ctx->vm_mutex, DNA_WAIT_FOREVER);
#endif
}
void lua_arch_unlock(void)
{
    struct lua_arch *ctx = &lua_arch_ctx;
#ifdef OS_LINUX
    pthread_mutex_unlock(&ctx->vm_mutex);
#elif defined(OS_DNA)
    dna_mutex_put(ctx->vm_mutex);
#endif
    // lua_log_dbg("put lock");
}

#ifdef OS_LINUX
static int get_script_size(const char *file)
{
    FILE *fp;

    fp = fopen(file, "rb");
    if (fp == NULL)
    {
        lua_log_err("open %s failed\n", file);
        return -1;
    }

    fseek(fp, 0, SEEK_END);
    int filesize = ftell(fp);

    fclose(fp);
    return filesize;
}

static int load_script(char *file, char *code, int buffsize)
{
    FILE *fp = NULL;
    int ret;

    fp = fopen(file, "rb");
    if (fp == NULL)
    {
        lua_log_err("open %s failed\n", file);
        ret = -1;
        goto fin;
    }

    fseek(fp, 0, SEEK_END);
    int filesize = ftell(fp);

    if (filesize > buffsize)
    {
        lua_log_err("buffsize %d not enough(%d)\n", buffsize, filesize);
        ret = -2;
        goto fin;
    }

    fseek(fp, 0, SEEK_SET);

    int codesize = fread(code, 1, filesize, fp);
    lua_log_inf("codesize %d filesize %d\n", codesize, filesize);
    ret = codesize;

fin:
    fclose(fp);
    return ret;
}
#endif

static lua_State *lua_arch_vm_start(char *code, int size)
{
    /* create a state and load standard library. */
    lua_State* l = luaL_newstate();
    if (l == NULL)
        return NULL;

    int err;

    /* opens all standard Lua libraries into the given state. */
    luaL_openlibs(l);
    print_lua_stack(l, "openlibs");

    /* register C function as lib which used by lua script */
    lua_module_init(l);

    if ((err = luaL_loadbuffer(l, code, size, "broadlink")) != LUA_OK) {
        lua_log_err("luaL_loadbuffer failed %d %s", err, lua_tostring(l, -1));
    }
    else {
        print_lua_stack(l, "loadbuffer code before pcall");

        if ((err = lua_pcall(l, 0, 0, 0)) != LUA_OK) {
            lua_log_err("lua_pcall failed %d %s", err, lua_tostring(l, -1));
        }
        else {
            print_lua_stack(l, "loadbuffer code after pcall");
            lua_log_inf("lua vm start");
            return l;
        }
    }

    // !!! something error happen
    lua_module_deinit(l);
    lua_close(l);
    return NULL;
}

static int lua_arch_vm_stop(lua_State *l)
{
    if (l == NULL)
        return -LUA_ARCH_ERR_VM;

    /* close lib first */
    lua_module_deinit(l);

    /* close lua state. */
    lua_close(l);
    lua_log_inf("lua stop");
    return LUA_ARCH_OK;
}

void lua_arch_data_output(int portid, unsigned char *pbuf, int len)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    int ret;

    switch (portid) {
        case PORT_ID_UART:
            dump_memory("via uart", pbuf, len);
            ret = LUA_ARCH_UART_WRITE(ctx->uart.hdl, pbuf, len);
            lua_log_dbg("uart write ret %d", ret);
            break;
        case PORT_ID_I2C:
            dump_memory("via i2c", pbuf, len);
            break;
        case PORT_ID_BL:
            dump_memory("via cloud_bl", pbuf, len);
            break;
        case PORT_ID_HI:
            dump_memory("via cloud_hi", pbuf, len);
            break;
        default:
            break;
    }
    lua_log_dbg("portid %d ret %d", portid, ret);
}

static void lua_arch_startup_handler(unsigned char *pbuf, unsigned int len)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    int mode = *(int *)pbuf;

    lua_log_dbg("startup len %d mode %d", len, mode);
    int ret = lua_arch_startup(ctx->l, mode);
    lua_log_dbg("lua_arch_startup ret %d", ret);
}

void lua_arch_netstate_notice(int status)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    if (ctx->syscom_hdl == NULL)
        return ;

    syscom_send(ctx->syscom_hdl, ctx->syscomid_net_notify, (unsigned char *)&status, sizeof(int));
}

static void lua_arch_net_notify(unsigned char *pbuf, unsigned int len)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    int status = *(int *)pbuf;

    lua_log_dbg("notify len %d status %d", len, status);
    int ret = lua_arch_network(ctx->l, status);
    lua_log_dbg("lua_arch_network ret %d", ret);
}

#ifdef OS_LINUX
#define SYSTEM_TICKS_MS(s, us)   (s + us * 1000)

static void *lua_arch_timer_polling(void *arg)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    struct lua_arch_uart *uart = &ctx->uart;
    int ret;
    struct timeval tv;

    lua_log_inf("uart pooling thread started");

    while (1) {
        usleep(LUA_ARCH_TIMER_PERIOD * 1000 * 6);

        if (ctx->uart.magic != LUA_ARCH_MAGIC)
            continue ;

        ret = LUA_ARCH_UART_READ(uart->hdl, uart->buff + uart->buffsize, uart->maxsize - uart->buffsize);
        // lua_log_dbg("uart read ret %d", ret);
        if (ret <= 0) {
            if (uart->buffsize > 0) {
                gettimeofday(&tv, NULL);
                if ((SYSTEM_TICKS_MS(tv.tv_sec, tv.tv_usec) - uart->tick) > uart->fit) {
                    syscom_send(ctx->syscom_hdl, ctx->syscomid_uart_hdl, uart->buff, uart->buffsize);
                    uart->buffsize = 0;
                }
            }
            continue ;
        }

        uart->buffsize += ret;
        gettimeofday(&tv, NULL);
        uart->tick = SYSTEM_TICKS_MS(tv.tv_sec, tv.tv_usec);
    }
    return NULL;
}

static int lua_arch_uart_hw_init(struct lua_arch_uart *uart)
{
    uart->hdl = uart_open(UART_DEV);
    if (uart->hdl < 0) {
        lua_log_err("uart %s open failed", UART_DEV);
        return -LUA_ARCH_FAILED;
    }
    int ret = uart_setopt(uart->hdl, uart->baud, uart->bits, uart->parity, uart->stop);
    if (ret != 0) {
        lua_log_err("uart %s setopt failed %d", UART_DEV, ret);
        return -LUA_ARCH_FAILED;
    }
    return LUA_ARCH_OK;
}
#elif defined(OS_DNA)
static void lua_arch_timer_polling(void *arg)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    struct lua_arch_uart *uart = &ctx->uart;
    int ret;

    if (ctx->uart.magic != LUA_ARCH_MAGIC)
        return ;

    ret = LUA_ARCH_UART_READ(uart->hdl, uart->buff + uart->buffsize, uart->maxsize - uart->buffsize);
    // lua_log_dbg("uart read ret %d", ret);
    if (ret <= 0) {
        if (uart->buffsize > 0) {
            if ((dna_system_ticks() - uart->tick) > uart->fit) {
                syscom_send(ctx->syscom_hdl, ctx->syscomid_uart_hdl, uart->buff, uart->buffsize);
                uart->buffsize = 0;
            }
        }
        return ;
    }

    uart->buffsize += ret;
    uart->tick = dna_system_ticks();
}

static int lua_arch_uart_hw_init(struct lua_arch_uart *uart)
{
    dna_uart_init(uart->id);
    uart->hdl = dna_uart_open(uart->id, uart->bits, uart->baud, \
                            uart->parity, uart->stop, \
                            DNA_UART_FLOW_CONTROL_NONE);
    if (uart->hdl == NULL) {
        lua_log_err("uart %d open failed", uart->id);
        return -LUA_ARCH_FAILED;
    }
    return LUA_ARCH_OK;
}
#endif

static void lua_arch_uart_handler(unsigned char *pbuf, unsigned int len)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    struct lua_arch_uart *uart = &ctx->uart;
    int ret;

    if (ctx->user_wait != 0) {
        /* this is resp from mcu cause clouds SET */
        if (len > ctx->cache.maxsize) {
            lua_log_wrn("cache maxsize %d not enough %d", ctx->cache.maxsize, len);
        }
        else {
            memcpy(ctx->cache.buff, pbuf, len);
            ctx->cache.len = len;

#ifdef OS_LINUX
            pthread_mutex_lock(&ctx->sema_mtx);
            pthread_cond_signal(&ctx->sema_cnd);
            pthread_mutex_unlock(&ctx->sema_mtx);
#elif defined(OS_DNA)
            dna_semaphore_put(ctx->sema);
#endif
            lua_log_inf("sema put");
        }
        return ;
    }

    dump_memory("uart bin2kv", pbuf, len);

    lua_log_dbg("bin2kv bin inlen %d snapshot %s", len, ctx->snapshot);
    ret = lua_arch_bin2kv(ctx->l, -1, pbuf, len, NULL, 0, \
                        ctx->snapshot.buff, ctx->snapshot.maxsize, NULL, 0);
    lua_log_dbg("lua_arch_bin2kv err %d", ret);
}

/**
 * @brief
 * @param  None
 * @retval None
 */
int lua_arch_init(void)
{
#define LUA_SCRIPT_PATH "apps/luaarch/lua_arch/opple.lua"

    struct lua_arch *ctx = &lua_arch_ctx;
    int err, ret = -LUA_ARCH_FAILED;

    memset(ctx, 0x0, sizeof(struct lua_arch));

    ctx->cache.buff = bincache;
    ctx->cache.maxsize = sizeof(bincache);
    ctx->cache.len = 0;

    ctx->snapshot.buff = snapshot_buff;
    ctx->snapshot.maxsize = sizeof(snapshot_buff);
    ctx->snapshot.len = 0;

    // for test
    strcpy(snapshot_buff, "{\"pwr\":0,\"name\":\"ted\"}");
    ctx->snapshot.len = strlen("{\"pwr\":0,\"name\":\"ted\"}");

    /* check and loading lua script */
    if ((err = get_script_size(LUA_SCRIPT_PATH)) < 0) {
        lua_log_err("get script size failed %d", err);
        return err;
    }

    ctx->scrisize = err;
    ctx->script = malloc(ctx->scrisize);
    if (ctx->script == NULL) {
        lua_log_err("malloc script %d failed", ctx->scrisize);
        return -LUA_ARCH_ERR_NO_MEM;
    }

    if ((err = load_script(LUA_SCRIPT_PATH, ctx->script, ctx->scrisize)) < 0) {
        lua_log_err("load script %d failed", err);
        ret = -LUA_ARCH_ERR_SCRIPT;
        goto back;
    }
    lua_log_inf("script size %d", ctx->scrisize);
    // lua_log_inf("%s", ctx->script);

#ifdef OS_LINUX
    pthread_mutex_init(&ctx->vm_mutex, NULL);
    pthread_mutex_init(&ctx->sema_mtx, NULL);
    pthread_cond_init(&ctx->sema_cnd, NULL);
#elif defined(OS_DNA)
    if ((ctx->vm_mutex = dna_mutex_create("lua")) == NULL) {
        lua_log_err("create lua mutex failed");
        ret = -LUA_ARCH_ERR_SYS;
        goto back;
    }

    if ((ctx->sema = dna_semaphore_create("lua", 1, 0)) == NULL) {
        lua_log_err("create lua sema failed");
        ret = -LUA_ARCH_ERR_SYS;
        goto back;
    }
#endif

    /* start lua virtual machine */
    if ((ctx->l = lua_arch_vm_start(ctx->script, ctx->scrisize)) == NULL) {
        lua_log_err("start lua vm failed");
        ret = -LUA_ARCH_ERR_VM;
        goto back;
    }

    /* hardware initialize */
    lua_arch_gpio_init(ctx->l);

    lua_arch_uart_init(ctx->l, &ctx->uart);
    if (lua_arch_uart_hw_init(&ctx->uart) != LUA_ARCH_OK)
        goto back2;

    ctx->uart.buff = uart_buff;
    ctx->uart.maxsize = sizeof(uart_buff);
    ctx->uart.buffsize = 0;
    ctx->uart.magic = LUA_ARCH_MAGIC;

#ifdef OS_LINUX
    if (pthread_create(&ctx->timer, NULL, lua_arch_timer_polling, NULL) != 0)
#elif defined(OS_DNA)
    if ((ctx->timer = dna_timer_create("lua_arch", \
                                    LUA_ARCH_TIMER_PERIOD,
                                    lua_arch_timer_polling,
                                    NULL)) == NULL)
#endif
    {
        lua_log_err("create uart timer failed");
        goto back2;
    }

    /* syscom initialize */
    syscom_init();

    ctx->syscom_hdl = syscom_open(LUA_SYSCOM_STACK_SIZE, bipbuffer, sizeof(bipbuffer));
    if (ctx->syscom_hdl == NULL) {
        lua_log_err("create syscom stack size %d bipbuff size %d failed", \
                    LUA_SYSCOM_STACK_SIZE, sizeof(bipbuffer));
        goto back2;
    }

    ctx->syscomid_net_notify = syscom_register(ctx->syscom_hdl, lua_arch_net_notify);
    if (ctx->syscomid_net_notify < 0) {
        lua_log_err("register syscomid_net_notify failed");
        goto back3;
    }
    lua_log_inf("regiseter syscomid_net_notify %d success", ctx->syscomid_net_notify);

    ctx->syscomid_uart_hdl = syscom_register(ctx->syscom_hdl, lua_arch_uart_handler);
    if (ctx->syscomid_uart_hdl < 0) {
        lua_log_err("register syscomid_uart_hdl failed");
        goto back3;
    }
    lua_log_inf("regiseter syscomid_uart_hdl %d success", ctx->syscomid_uart_hdl);

    ctx->syscomid_startup = syscom_register(ctx->syscom_hdl, lua_arch_startup_handler);
    if (ctx->syscomid_startup < 0) {
        lua_log_err("register syscomid_startup failed");
        goto back3;
    }
    lua_log_inf("regiseter syscomid_startup %d success", ctx->syscomid_startup);

    /* service initialize */
    if (lua_arch_timer_init() != LUA_ARCH_OK)
        goto back3;

    lua_arch_devinfo_init(ctx->l, &ctx->devinfo);

    lua_arch_clouds_init(ctx->l, &ctx->cldsmask);

    int boottype = 1;
    syscom_send(ctx->syscom_hdl, ctx->syscomid_startup, &boottype, sizeof(int));

    lua_log_inf("lua arch init done");
    return LUA_ARCH_OK;

back3:
    syscom_close(ctx->syscom_hdl);
back2:
    lua_arch_stop(ctx->l);
back:
    free(ctx->script);
    return ret;
}

/**
 * @brief
 * @param  None
 * @retval None
 */
static void (*tc_func[])(lua_State *L) =
{
    tc5_run,
};

int lua_arch_start(void)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    int i;

    if (ctx->l == NULL) {
        lua_log_err("lua vm err");
        return -LUA_ARCH_ERR_VM;
    }

    // batch TESTS running
    for (i = 0; i < DeepOfArray(tc_func); ++i) {
        lua_log_inf("==== testcase %d run ====", i);
        (*tc_func[i])(ctx->l);
    }

    return LUA_ARCH_OK;
}

int lua_arch_stop(void)
{
    struct lua_arch *ctx = &lua_arch_ctx;

    if (ctx->l == NULL) {
        lua_log_err("lua vm err");
        return -LUA_ARCH_ERR_VM;
    }

    lua_arch_uart_deinit(ctx->l);
    lua_arch_gpio_deinit(ctx->l);

    int ret = lua_arch_vm_stop(ctx->l);
    free(ctx->script);

#ifdef OS_LINUX
    pthread_mutex_destroy(&ctx->vm_mutex);
#elif defined(OS_DNA)
    dna_mutex_delete(ctx->vm_mutex);
#endif

    return ret;
}

char *lua_arch_get_snapshot(int *size)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    lua_log_dbg("snapshot %p snapsize %d", ctx->snapshot.buff, ctx->snapshot.len);
    *size = ctx->snapshot.len;
    return ctx->snapshot.buff;
}

struct lua_arch_devinfo *lua_arch_get_devinfo(void)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    return &ctx->devinfo;
}

int lua_arch_register_syscom(void (*handler)(unsigned char *pbuf, unsigned int len))
{
    struct lua_arch *ctx = &lua_arch_ctx;
    int id;

    if (ctx->syscom_hdl == NULL)
        return -LUA_ARCH_ERR_SYSCOM;

    id = syscom_register(ctx->syscom_hdl, handler);
    if (id < 0) {
        lua_log_err("register syscomid failed");
        return -LUA_ARCH_ERR_SYSCOM;
    }

    return id;
}

int lua_arch_syscom_send(unsigned short syscom_id, unsigned char *pbuf, int len)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    return syscom_send(ctx->syscom_hdl, syscom_id, pbuf, len);
}

int lua_arch_uart_send(unsigned char *pbuf, int len)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    struct lua_arch_uart *uart = &ctx->uart;

    if (uart->hdl == NULL)
        return -LUA_ARCH_ERR_SYS;

    int ret = LUA_ARCH_UART_WRITE(uart->hdl, pbuf, len);
    lua_log_dbg("uart write len %d ret %d", len, ret);
    return ret;
}

int lua_arch_kv_handler(unsigned int cloud_id, \
                const unsigned char *input, unsigned int inlen, \
                unsigned char *output, unsigned int maxsize)
{
    struct lua_arch *ctx = &lua_arch_ctx;
    struct lua_arch_uart *uart = &ctx->uart;
    int result, waiting_ms;

    lua_log_dbg("kv2bin inlen %d snapshot %s", inlen, ctx->snapshot.buff);
    if ((result = lua_arch_kv2bin(ctx->l, cloud_id, input, inlen, \
                                ctx->snapshot.buff, ctx->snapshot.maxsize, \
                                output, maxsize)) != LUA_ARCH_OK) {
        lua_log_err("lua_arch_kv2bin err %d", result);
        return -LUA_ARCH_ERR_SCRIPT;
    }

    /* clouds return (generally is clouds GET) */
    if ((result = strlen(output)) > 0) {
        dump_memory("lua_arch_kv_handler output", output, result);
        return result;
    }

    /* (generally is clouds SET) */
    ctx->user_wait = !0;
    waiting_ms = uart->fit * 10;
    lua_log_inf("wait in %dms", waiting_ms);
#ifdef OS_LINUX
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = waiting_ms * 1000;
    pthread_mutex_lock(&ctx->sema_mtx);
    result = pthread_cond_timedwait(&ctx->sema_cnd, &ctx->sema_mtx, &ts);
    pthread_mutex_unlock(&ctx->sema_mtx);
    if (result == ETIMEDOUT)
        result = -LUA_ARCH_FAILED;
    else
        result = LUA_ARCH_OK;
#elif defined(OS_DNA)
    result = dna_semaphore_get(ctx->sema, waiting_ms);
#endif
    ctx->user_wait = 0;
    lua_log_inf("sema get");

    if (result != LUA_ARCH_OK) {
        /* wait mcu data timeout */
        lua_log_dbg("bin2kv snapshot %s", ctx->snapshot.buff);
        if ((result = lua_arch_bin2kv(ctx->l, cloud_id, NULL, 0, input, inlen, \
                                    ctx->snapshot.buff, ctx->snapshot.maxsize, \
                                    output, maxsize)) != LUA_ARCH_OK) {
            lua_log_err("lua_arch_bin2kv err %d", result);
            return -LUA_ARCH_ERR_SCRIPT;
        }

        result = strlen(output);
        lua_log_dbg("bin2kv kvout %d:%s snapshot %s", result, output, ctx->snapshot.buff);
    }
    else {
        dump_memory("uart cache", ctx->cache.buff, ctx->cache.len);
        lua_log_dbg("bin2kv snapshot %s", ctx->snapshot.buff);
        if ((result = lua_arch_bin2kv(ctx->l, cloud_id, ctx->cache.buff, ctx->cache.len, \
                                    input, inlen, ctx->snapshot.buff, ctx->snapshot.maxsize, \
                                    output, maxsize)) != LUA_ARCH_OK) {
            lua_log_err("lua_arch_bin2kv err %d", result);
            return -LUA_ARCH_ERR_SCRIPT;
        }

        result = strlen(output);
        lua_log_dbg("bin2kv kvout %d:%s snapshot %s", result, output, ctx->snapshot.buff);
    }

    return result;
}

/* End of file */
